# -----------------------------------------------------
# Author: Yaqiang Wang
# Date: 2018-7-18
# Purpose: MeteoInfo index module
# Note: Jython
# -----------------------------------------------------

from org.meteoinfo.dataframe import Index as MIIndex
from org.meteoinfo.dataframe import DateTimeIndex as MIDateTimeIndex
from java.time import LocalDateTime

import datetime
import numbers

import mipylib.numeric as np
import mipylib.miutil as miutil
import series


class Index(object):

    @staticmethod
    def factory(data=None, name='Index', index=None):
        """
        Factory method
        """
        if index is None:
            if isinstance(data[0], (LocalDateTime, datetime.datetime)):
                return DateTimeIndex(data, name)
            else:
                return Index(data, name)
        else:
            if isinstance(index, MIDateTimeIndex):
                return DateTimeIndex(index=index)
            else:
                return Index(index=index)

    def __init__(self, data=None, name='Index', index=None):
        """
        Index 
        
        :param data: (*array_like*) Index values
        :param name: (*string*) Index name                
        """
        if index is None:
            if isinstance(data, np.NDArray):
                data = data.aslist()
            self.data = data
            self._index = MIIndex.factory(data)
        else:
            self._index = index
            self.data = list(self._index.getData())
        self._index.setName(name)

    @property
    def name(self):
        return self._index.getName()

    @name.setter
    def name(self, value):
        self._index.setName(value)

    def __len__(self):
        return self._index.size()

    def __iter__(self):
        """
        provide iteration over the values of the Index
        """
        return iter(self._index)

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        return self._index.toString()

    def __getitem__(self, k):
        if isinstance(k, int):
            return self.data[k]
        else:
            sidx = 0 if k.start is None else k.start
            if sidx < 0:
                sidx = self.__len__() + sidx
            eidx = self.__len__() if k.stop is None else k.stop
            if eidx < 0:
                eidx = self.__len__() + eidx
            step = 1 if k.step is None else k.step
            r = self._index.subIndex(sidx, eidx, step)
            return Index.factory(index=r)

    def __eq__(self, other):
        if isinstance(other, numbers.Number):
            return np.NDArray(self._index.equal(other))
        else:
            return False

    def index(self, v):
        """
        Get index of a value.
        
        :param v: (*object*) value
        
        :returns: (*int*) Value index
        """
        return self._index.indexOf(v)

    def get_loc(self, key, outkeys=False):
        """
        Get integer location, slice or boolean mask for requested label.
        
        :param key: (*string or list*) Label.
        :param outkeys: (*boolean*) If return location keys or not.
        
        :returns: int if unique index, slice if monotonic index, else mask.
        """
        if isinstance(key, series.Series):
            key = key.values

        if isinstance(key, np.NDArray) and key.dtype == np.dtype.bool:
            r = self._index.filterIndices(key.asarray())
            return list(r)
        else:
            if isinstance(key, np.NDArray):
                r = self._index.getIndices(key.asarray())
            else:
                r = self._index.getIndices(key)
            if outkeys:
                return list(r[0]), list(r[1])
            else:
                return list(r[0])

    def fill_keylist(self, rdata, rfdata):
        return self._index.fillKeyList(rdata.asarray(), rfdata)

    def get_indexer(self, key):
        """
        Compute indexer and mask for new index given the current index.

        :param key: Index.

        :return: (*array*) Integers from 0 to n - 1 indicating that the index at these positions matches
            the corresponding target values. Missing values in the target are marked by -1.
        """
        if isinstance(key, np.NDArray):
            r = self._index.getIndices(key.asarray())
        else:
            r = self._index.getIndices(key)

        return list(r[2])

    def get_format(self):
        """
        Get value to string format.
        
        :returns: (*string*) Format string.
        """
        return self._index.getFormat()

    def set_format(self, format):
        """
        Set value to string format.
        
        :param format: (*string*) Format string.
        """
        self._index.setFormat(format)


############################################
class DateTimeIndex(Index):

    def __init__(self, data=None, name='Index', start=None, end=None, periods=None, freq='D', index=None):
        if index is None:
            if not data is None:
                if isinstance(data, np.NDArray):
                    data = data.aslist()
                self.data = data
                if isinstance(data[0], datetime.datetime):
                    self._index = MIDateTimeIndex(miutil.jdate(data))
                else:
                    self._index = MIDateTimeIndex(data)
            else:
                if start is None:
                    self._index = MIDateTimeIndex(periods, end, freq)
                elif end is None:
                    self._index = MIDateTimeIndex(start, periods, freq)
                else:
                    self._index = MIDateTimeIndex(start, end, freq)
                self.data = miutil.pydate(list(self._index.getData()))
        else:
            self._index = index
            self.data = miutil.pydate(list(self._index.getData()))
        self._index.setName(name)

    def index(self, v):
        """
        Get index of a value.
        
        :param v: (*datetime or string*) Date time value
        
        :returns: (*int*) Value index
        """
        if isinstance(v, datetime.datetime):
            v = miutil.jdatetime(v)
        else:
            v = miutil.str2jdate(v)
        return self._index.indexOf(v)

    def get_loc(self, key, outkeys=False):
        """
        Get integer location, slice or boolean mask for requested label.
        
        :param key: (*string or list*) Label.
        :param outkeys: (*boolean*) If return location keys or not.
        
        :returns: int if unique index, slice if monotonic index, else mask.
        """
        if isinstance(key, np.NDArray) and key.dtype.kind == 'b':
            r = self._index.filterIndices(key.asarray())
            return list(r)
        elif isinstance(key, datetime.datetime):
            key = miutil.jdatetime(key)
        elif isinstance(key, (list, tuple, np.NDArray)) and isinstance(key[0], datetime.datetime):
            key = miutil.jdatetime(key)
        r = self._index.getIndices(key)
        if outkeys:
            return list(r[0]), list(r[1])
        else:
            return list(r[0])

    @property
    def year(self):
        """
        Get year index.
        """
        r = self._index.getYear()
        return Index(index=r)

    @property
    def month(self):
        """
        Get month index.
        """
        r = self._index.getMonth()
        return Index(index=r)

    @property
    def day(self):
        """
        Get day index.
        """
        r = self._index.getDay()
        return Index(index=r)

    @property
    def hour(self):
        """
        Get hour index.
        """
        r = self._index.getHour()
        return Index(index=r)

    @property
    def minute(self):
        """
        Get minute index.
        """
        r = self._index.getMinute()
        return Index(index=r)

    @property
    def second(self):
        """
        Get second index.
        """
        r = self._index.getSecond()
        return Index(index=r)


#############################################
def date_range(start=None, end=None, periods=None, freq='D'):
    """
    Create DateTimeIndex by date range.
    
    :param start: (*string or datetime*) Start date time.
    :param end: (*string or datetime*) End date time.
    :param periods: (*int*) Periods number.
    :param freq: (*string*) Date time frequent value [ Y | M | D | h | min | s ].
    
    :returns: (*DateTimeIndex*) DateTimeIndex
    """
    r = DateTimeIndex(start=start, end=end, periods=periods, freq=freq)
    return r
